package plus.easydo.starter.oauth.server.configure;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import plus.easydo.starter.oauth.core.config.SecurityBeanConfig;
import plus.easydo.starter.oauth.server.exception.CustomizeOauth2WebResponseExceptionTranslator;
import plus.easydo.starter.oauth.server.properties.Oauth2ServerProperties;
import plus.easydo.starter.oauth.server.serializer.FastJson2RedisTokenStoreSerializationStrategy;
import plus.easydo.starter.oauth.server.service.CustomizeAuthorizationCodeServices;
import plus.easydo.starter.oauth.server.service.CustomizeDefaultUserAuthenticationConverter;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @author laoyu
 * @version 1.0
 */
@Configuration("serverBeanConfig")
@EnableConfigurationProperties(Oauth2ServerProperties.class)
public class ServerBeanConfig implements SecurityBeanConfig<Oauth2ServerProperties> {

    @Resource
    Oauth2ServerProperties oauth2ServerProperties;
    @Resource
    AuthorizationServerTokenServices authorizationServerTokenServices;
    @Resource
    WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator;
    @Resource
    private DataSource dataSource;
    @Resource
    private PasswordEncoder passwordEncoder;
    /**
     * 认证管理器
     */
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private JdbcClientDetailsService jdbcClientDetailsService;
    @Resource
    private AuthorizationCodeServices authorizationCodeServices;
    @Resource
    private AccessTokenConverter accessTokenConverter;

    @Override
    public JdbcClientDetailsService getClientDetailsService() {
        return this.jdbcClientDetailsService;
    }

    @Override
    public AuthorizationServerTokenServices getAuthorizationServerTokenServices() {
        return authorizationServerTokenServices;
    }

    public PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    @Override
    public Oauth2ServerProperties properties() {
        return oauth2ServerProperties;
    }


    /**
     * 密码编码验证器
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * token令牌 服务
     *
     * @param clientDetailsService clientDetailsService
     * @param tokenStore tokenStore
     * @return org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices
     * @author laoyu
     */
    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices(ClientDetailsService clientDetailsService, TokenStore tokenStore) {
        DefaultTokenServices services = new DefaultTokenServices();
        /*客户端的配置信息*/
        services.setClientDetailsService(clientDetailsService);
        /*支持刷新token*/
        services.setSupportRefreshToken(true);
        /*关联存储方式*/
        services.setTokenStore(tokenStore);
        /*令牌默认的有效期2小时*/
        services.setAccessTokenValiditySeconds(oauth2ServerProperties.getAccessTokenValiditySeconds());
        /*刷新令牌默认的有效期3天*/
        services.setRefreshTokenValiditySeconds(oauth2ServerProperties.getRefreshTokenValiditySeconds());
        return services;
    }

    /**
     * 授权码相关
     *
     * @return org.springframework.security.oauth2.provider.code.AuthorizationCodeServices
     * @author laoyu
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new CustomizeAuthorizationCodeServices();
    }

    /**
     * 用户认证转换器
     *
     * @return org.springframework.security.oauth2.provider.token.AccessTokenConverter
     * @author laoyu
     */
    @Bean
    public AccessTokenConverter accessTokenConverter() {
        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter defaultUserAuthenticationConverter =
                new CustomizeDefaultUserAuthenticationConverter(oauth2ServerProperties);
        defaultAccessTokenConverter.setUserTokenConverter(defaultUserAuthenticationConverter);
        return defaultAccessTokenConverter;
    }


    /**
     * Token信息存储在Redis中
     *
     * @param lettuceConnectionFactory lettuceConnectionFactory
     * @return TokenStore
     */
    @Bean
    public TokenStore tokenStore(LettuceConnectionFactory lettuceConnectionFactory) {
        final RedisTokenStore tokenStore = new RedisTokenStore(lettuceConnectionFactory);
        /*token前缀*/
        tokenStore.setPrefix(oauth2ServerProperties.getOathTokenPrefix());
        if (oauth2ServerProperties.isEnableFastJsonSerializer()) {
            //自定义json进行序列化和反序列化
            FastJson2RedisTokenStoreSerializationStrategy<Object> serializer = new FastJson2RedisTokenStoreSerializationStrategy<>(Object.class);
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            /*指定fastJson序列化输入类型*/
            objectMapper.activateDefaultTyping(
                    LaissezFaireSubTypeValidator.instance,
                    ObjectMapper.DefaultTyping.NON_FINAL,
                    JsonTypeInfo.As.WRAPPER_ARRAY);
            serializer.setObjectMapper(objectMapper);
            /*初始化redis*/
            lettuceConnectionFactory.afterPropertiesSet();
            tokenStore.setSerializationStrategy(serializer);
        }
        return tokenStore;
    }

    @Bean
    public WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator() {
        return new CustomizeOauth2WebResponseExceptionTranslator();
    }

    public AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public AuthorizationCodeServices getAuthorizationCodeServices() {
        return authorizationCodeServices;
    }

    public AccessTokenConverter getAccessTokenConverter() {
        return accessTokenConverter;
    }

    public WebResponseExceptionTranslator<OAuth2Exception> getWebResponseExceptionTranslator() {
        return webResponseExceptionTranslator;
    }
}
